package com.lp.article.service.impl;

import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.lp.model.constants.MQConstants;
import com.lp.article.mapper.ArticleMapper;
import com.lp.article.mapper.ArticleStatMapper;
import com.lp.article.service.IArticleService;
import com.lp.feign.UserClients;
import com.lp.model.config.AppResult;
import com.lp.model.config.CodeMsg;
import com.lp.model.constants.RedisConstants;
import com.lp.model.dto.article.*;
import com.lp.model.entity.article.Article;
import com.lp.model.entity.article.ArticleStat;
import com.lp.model.entity.article.Comments;
import com.lp.model.entity.article.vo.ArticleESVO;
import com.lp.model.entity.article.vo.PageVO;
import com.lp.model.entity.user.User;
import com.lp.model.entity.article.vo.ArticleVO;
import com.lp.model.exception.ApplicationException;
import com.lp.model.utils.PageUtils;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;

@Slf4j
@Service
public class ArticleService implements IArticleService {

    @Autowired
    private ArticleMapper articleMapper;

    @Autowired
    private ArticleStatMapper articleStatMapper;

    @Autowired
    private UserClients userClients;

    @Autowired
    private StringRedisTemplate redisTemplate;

    @Autowired
    private RabbitTemplate rabbitTemplate;

    @Autowired
    private ObjectMapper objectMapper;

    @Override
    public PageVO<ArticleVO> getNewList(PageDto pageDto) {
        //1.解析分页数据，获取文章列表
        int limit = pageDto.getPageSize();
        int offset = (pageDto.getCurPage() - 1) * pageDto.getPageSize();
        List<Article> articleList = articleMapper.selectNewList(limit, offset);
        //tip: 还没有文章数据
        if(articleList == null) {
            return null;
        }
        //2.远程调用获取用户数据，从数据库中获取统计信息，整合 VO 数据并返回
        List<ArticleVO> articleVOList = getArticleVONewList(articleList);
        //3.获取文章总数
        Long total = articleMapper.selectTotal();
        //4.返回分页数据
        return PageUtils.getPageVO(articleVOList, total);
    }

    @Override
    public void publish(Article article) {
        //1.新增文章(按理来说，这里要先经过审核才可以)
        articleMapper.add(article);
        //2.新增统计数据
        //1) 获取最近发布的文章数据(主要是获取 id)
        Long articleId = articleMapper.selectArticleByLastTime().getId();
        //2) 添加统计信息
        articleStatMapper.add(articleId,article.getUserId());
        log.info("发布新文章！articleId: {}, userId: {}", articleId, article.getUserId());
        //3.通过 mq 同步到 es 中
        mqSendInsertArticleMsgToES(articleId);
    }

    /**
     * 新增文章同步消息到 es 中
     * @param articleId 文章 id
     */
    @SneakyThrows
    private void mqSendInsertArticleMsgToES(Long articleId) {
        String msg = objectMapper.writeValueAsString(articleId);
        rabbitTemplate.convertAndSend(MQConstants.ES_EXCHANGE_DIRECT,
                MQConstants.ES_ARTICLE_INSERT_QUEUE, msg);
    }

    @SneakyThrows
    @Override
    public PageVO<ArticleVO> getSelfList(PageDto pageDto) {
        //1.解析
        //1) 解析分页数据
        int limit = pageDto.getPageSize();
        int offset = (pageDto.getCurPage() - 1) * pageDto.getPageSize();
        //2) 解析用户数据
        List<Article> articles = articleMapper.selectMyArticleList(pageDto.getId(), limit, offset);
        //2.获取文章统计数据，整合到 VO 中(不带个人用户数据)
        List<ArticleVO> articleVOMyList = getArticleVOMyList(articles);
        //3.获取个人文章总数
        Long articleTotal = articleMapper.selectMyArticleTotal(pageDto.getId());
        //4.返回分页信息
        return PageUtils.getPageVO(articleVOMyList, articleTotal);
    }

    @Override
    public void markDel(Long id) {
        //1.标记删除文章
        articleMapper.markDel(id);
        //3.删除 redis 上的缓存数据
        delRedisDataById(id);
        //2.es 同步删除文章
        mqSendDeleteArticleMsgToES(id);
    }


    /**
     * 删除 redis 上对应的文章数据(一条指令删除)
     * @param id 文章 id
     */
    private void delRedisDataById(Long id) {
        List<String> keys = new ArrayList<>();
        keys.add(RedisConstants.ARTICLE_VISITS + id);
        keys.add(RedisConstants.ARTICLE_LIKE + id);
        keys.add(RedisConstants.ARTICLE_COLLECT + id);
        keys.add(RedisConstants.ARTICLE_COMMENT + id);
        redisTemplate.delete(keys);
    }

    /**
     * es 同步删除文章
     * @param id 文章id
     */
    @SneakyThrows
    private void mqSendDeleteArticleMsgToES(Long id) {
        String msg = objectMapper.writeValueAsString(id);
        rabbitTemplate.convertAndSend(MQConstants.ES_EXCHANGE_DIRECT,
                MQConstants.ES_ARTICLE_DEL_QUEUE, msg);
    }

    @Override
    public void update(Article article) {
        //1.非空校验
        if(article == null || article.getBoardName() == null || article.getId() == null ||
                !StringUtils.hasLength(article.getTitle()) ||
                !StringUtils.hasLength(article.getDesc()) ||
                !StringUtils.hasLength(article.getContent())) {
            log.info(CodeMsg.FAIL_NULL_ARTICLE.toString());
            throw new ApplicationException(AppResult.fail(CodeMsg.FAIL_NULL_ARTICLE));
        }
        //2.修改文章
        articleMapper.update(article);
        //3.mq 通知 es 同步修改文章内容
        mqSendUpdateArticleMsgToES(article.getId());
    }

    /**
     * mq 通知 es 同步修改文章内容
     */
    @SneakyThrows
    private void mqSendUpdateArticleMsgToES(Long articleId) {
        String msg = objectMapper.writeValueAsString(articleId);
        rabbitTemplate.convertAndSend(MQConstants.ES_EXCHANGE_DIRECT,
                MQConstants.ES_ARTICLE_UPDATE_QUEUE, msg);
    }

    @Override
    public ArticleVO getInfo(Long id) {
        //1.非空校验
        if(id == null) {
            log.info(CodeMsg.FAIL_NULL_ARTICLE_ID.toString());
            throw new ApplicationException(AppResult.fail(CodeMsg.FAIL_NULL_ARTICLE_ID));
        }
        //2.获取文章信息
        Article article = articleMapper.selectArticleById(id);
        //3.从 redis 上获取文章统计信息
        //如果数据不存在(redis 上过期)，就从 mysql 上获取，然后再将处理后的信息通过 mq 同步到 mysql 中
        ArticleStat articleStat = getStatByRedis(id);
        //4.整合到 VO 中
        ArticleVO articleVO = getArticleVO(article, articleStat);
        return articleVO;
    }

    @Override
    public List<ArticleStat> getStatByUserId(Long userId) {
        //1.非空校验
        if(userId == null) {
            log.info(CodeMsg.FAIL_NULL_USER.toString());
            throw new ApplicationException(AppResult.success(CodeMsg.FAIL_NULL_USER));
        }
        //2.获取统计信息
        List<ArticleStat> articleStats = articleStatMapper.selectStatByUserId(userId);
        return articleStats;
    }

    @SneakyThrows
    @Override
    public ArticleESVO getArticleESVO(Long id) {
        //1.获取文章信息
        Article article = articleMapper.selectArticleById(id);
        if(article == null) {
            log.info(CodeMsg.FAIL_ERROR_ARTICLE_ID.toString());
            throw new ApplicationException(AppResult.fail(CodeMsg.FAIL_ERROR_ARTICLE_ID));
        }
        //2.获取用户信息
        String info = userClients.info(article.getUserId());
        AppResult result = objectMapper.readValue(info, AppResult.class);
        User user = objectMapper.convertValue(result.getData(), new TypeReference<User>() {});
        //4.整合到 VO 中
        return getArticleVO(article, user);
    }

    @Override
    public ArticleStat getArticleStat(Long id) {
        ArticleStat articleStat = articleStatMapper.selectStatByArticleId(id);
        if(articleStat == null) {
            log.info(CodeMsg.FAIL_ERROR_ARTICLE_ID.toString());
            throw new ApplicationException(AppResult.fail(CodeMsg.FAIL_ERROR_ARTICLE_ID));
        }
        return articleStat;
    }

    /**
     * 整合文章 VO
     */
    private ArticleESVO getArticleVO(Article article, User user) {
        ArticleESVO articleESVO = new ArticleESVO();
        BeanUtils.copyProperties(article, articleESVO);
        articleESVO.setNickname(user.getNickname());
        articleESVO.setPhotoUrl(user.getPhotoUrl());
        return articleESVO;
    }

    /**
     * 整合文章 VO
     */
    private ArticleVO getArticleVO(Article article, ArticleStat articleStat) {
        ArticleVO articleVO = new ArticleVO();
        BeanUtils.copyProperties(article, articleVO);
        articleVO.setVisits(articleStat.getVisits());
        articleVO.setLikeCount(articleStat.getLikeCount());
        articleVO.setCollectCount(articleStat.getCollectCount());
        articleVO.setCommentCount(articleStat.getCommentCount());
        return articleVO;
    }

    /**
     * 从 redis 上获取文章统计信息
     * 如果数据不存在(redis 上过期)，就从 mysql 上获取，然后再将处理后的信息通过 mq 同步到 mysql 中
     */
    @SneakyThrows
    private ArticleStat getStatByRedis(Long id) {
        ArticleStat articleStat = new ArticleStat();
        //1.获取统计数据
        //1) 获取播放量
        String visits = redisTemplate.opsForValue().get(RedisConstants.ARTICLE_VISITS + id);
        //2) 获取点赞量
        Long likeCount = redisTemplate.opsForSet().size(RedisConstants.ARTICLE_LIKE + id);
        //3) 获取收藏量
        Long collectCount = redisTemplate.opsForSet().size(RedisConstants.ARTICLE_COLLECT + id);
        //4) 获取评论量
        String commentCount = redisTemplate.opsForValue().get(RedisConstants.ARTICLE_COMMENT + id);
        //2.若其中任意一个等于 null，就从 MySQL 中获取，然后将增加的访问量通过 mq 异步到数据库
        if(visits == null || commentCount == null) {
            //1) 获取文章统计信息
            articleStat = articleStatMapper.selectStatByArticleId(id);
            //2) 将数据同步到 redis 上
            String msg = objectMapper.writeValueAsString(articleStat);
            rabbitTemplate.convertAndSend(MQConstants.ARTICLE_EXCHANGE_DIRECT,
                    MQConstants.ARTICLE_STAT_QUEUE, msg);
        } else {
            //1) 将 redis 上获取的数据放入 stat 中
            articleStat.setVisits(Long.valueOf(visits));
            articleStat.setLikeCount(Math.toIntExact(likeCount));
            articleStat.setCollectCount(Math.toIntExact(collectCount));
            articleStat.setCommentCount(Integer.valueOf(commentCount));
            //2) redis 访问量 + 1
            Long visitsByIncr = redisTemplate.opsForValue().increment(RedisConstants.ARTICLE_VISITS + id);
            //3) 将访问量 + 1 操作通过 mq 同步到 MySQL 中
            HashMap<String, Object> map = new HashMap<>();
            map.put("articleId", id);
            map.put("visits", visitsByIncr);
            String msg = objectMapper.writeValueAsString(map);
            rabbitTemplate.convertAndSend(MQConstants.ARTICLE_EXCHANGE_DIRECT,
                    MQConstants.ARTICLE_VISITS_QUEUE, msg);
        }
        return articleStat;
    }

    /**
     * 获取文章统计数据，整合到 VO 中(不带个人用户数据)
     * @param articles 个人文章列表
     */
    private List<ArticleVO> getArticleVOMyList(List<Article> articles) {
        //1.获取文章统计信息
        List<ArticleVO> articleVOMyList = new ArrayList<>();
        for(Article article : articles) {
            ArticleStat articleStat = articleStatMapper.selectStatByArticleId(article.getId());
            ArticleVO articleVO = new ArticleVO();
            BeanUtils.copyProperties(article, articleVO);
            articleVO.setVisits(articleStat.getVisits());
            articleVO.setLikeCount(articleStat.getLikeCount());
            articleVO.setCommentCount(articleStat.getCommentCount());
            articleVO.setCollectCount(articleStat.getCollectCount());
            articleVOMyList.add(articleVO);
        }
        return articleVOMyList;
    }

    /**
     * 远程调用获取用户数据，从数据库中获取统计信息，整合 VO 数据并返回
     * @param articleList 文章列表
     */
    @SneakyThrows
    private List<ArticleVO> getArticleVONewList(List<Article> articleList) {
        List<ArticleVO> articleVOList = new ArrayList<>();
        for(Article article : articleList) {
            //1.远程调用拿到文章对应用户数据
            String userinfo = userClients.info(article.getUserId());
            //2.解析 feign 自动转化的 json 格式
            AppResult result = objectMapper.readValue(userinfo, AppResult.class);
            //3.解析出用户信息
            User user = objectMapper.convertValue(result.getData(), new TypeReference<User>() {});
            //4.获取文章对应的统计信息
            ArticleStat articlestat = articleStatMapper.selectStatByArticleId(article.getId());
            //5.整合到 VO 中
            ArticleVO articleVO = new ArticleVO();
            BeanUtils.copyProperties(article, articleVO);
            articleVO.setContent(""); //列表展示不需要正文，只需要 desc 描述即可
            //1) 用户数据
            articleVO.setNickname(user.getNickname());
            articleVO.setPhotoUrl(user.getPhotoUrl());
            //2) 统计数据
            articleVO.setVisits(articlestat.getVisits());
            articleVO.setLikeCount(articlestat.getLikeCount());
            articleVO.setCommentCount(articlestat.getCommentCount());
            articleVO.setCollectCount(articlestat.getCollectCount());
            articleVOList.add(articleVO);
        }
        return articleVOList;
    }

}
